package client_core

import (
	"context"
	"fmt"
	"github.com/chicken-team-outside/chicken_transmission/client_core/config"
	"github.com/chicken-team-outside/chicken_transmission/client_core/service"
	"github.com/chicken-team-outside/chicken_transmission/model"
	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"runtime"
	"sync"
)

var downloadTaskRunnerMgr *TaskRunnerMgr
var bytes65535Pool = sync.Pool{
	New: func() any {
		return make([]byte, 65535)
	},
}

func initDownloadMgr(ctx context.Context, group *gin.RouterGroup) {
	group.POST("/delete", deleteDownloadTask)
	group.POST("/pause", pauseDownloadTask)
	group.POST("/resume", resumeDownloadTask)
	group.POST("/add", addDownloadTask)
	downloadTaskRunnerMgr = NewTaskRunnerMgr(ctx, true)
	err := service.UpdateAllTaskStatus([]model.TaskType{model.TaskType_DownloadFile, model.TaskType_DownloadDir}, canPauseStatus, model.TaskStatus_Pausing, "")
	if err != nil {
		zap.L().Fatal("failed to update download task status", zap.Error(err))
	}
	downloadTaskRunnerMgr.Startup(runtime.NumCPU(), runtime.NumCPU())
}

func (m *TaskRunnerMgr) runDownloadTaskCb(task *TaskWarp) error {
	maxWorkers := config.DownloadMaxWorkers.Load()
	downloadMaxRetries := config.DownloadMaxRetries.Load()
	blockSize := config.DownloadBlockSize.Load()
	buf := bytes65535Pool.Get().([]byte)
	defer bytes65535Pool.Put(buf)
	downloader, err := NewDownloader(task, blockSize, downloadMaxRetries, maxWorkers, buf)
	if err != nil {
		return err
	}
	return downloader.Run()
}

func (m *TaskRunnerMgr) deleteDownloadTaskCb(ids []int64) (err1 error) {
	err1 = m.pauseDownloadTaskCb(ids)
	if err1 == nil {
		err1 = m.fileRunner.opWithLock(true, func() (err error) {
			tasks, err := service.FindTaskInfosByStatus(model.TaskType_DownloadFile, canDeleteStatus, ids)
			if len(tasks) > 0 {
				ids = ids[:0]
				for _, task := range tasks {
					err = DeleteStatusFileAndTmpFile(&task.LocalPath)
					if err != nil {
						zap.L().Warn("failed to delete download task tmp file", zap.Int64("id", task.Id), zap.Error(err))
					} else {
						ids = append(ids, task.Id)
					}
				}
				if len(ids) > 0 {
					err = service.DeleteTaskByIds(model.TaskType_DownloadFile, ids)
					if err != nil {
						err = fmt.Errorf("failed to delete task: %w", err)
					}
					sendTaskStatusChangeByIds(model.TaskType_DownloadFile, TaskMgrOpTypeDeleteTask, ids)
				}
			}
			return
		})
	}
	return
}

func (m *TaskRunnerMgr) pauseDownloadTaskCb(ids []int64) error {
	return m.fileRunner.opWithLock(true, func() (err error) {
		ids, err = service.FindTasksByStatus(model.TaskType_DownloadFile, canPauseStatus, ids)
		if len(ids) > 0 {
			m.fileRunner.taskLock.Lock()
			defer m.fileRunner.taskLock.Unlock()
			var waiting []int64
			for _, id := range ids {
				if warp, ok := m.fileRunner.tasks[id]; ok {
					warp.stop()
				} else {
					waiting = append(waiting, id)
				}
			}
			sendTaskStatusChangeByIds(model.TaskType_DownloadFile, TaskMgrOpTypePauseTask, ids)
			if len(waiting) > 0 {
				err = service.UpdateTasksStatus(model.TaskType_DownloadFile, canPauseStatus, model.TaskStatus_Paused, "", waiting)
			}
			if err != nil {
				err = fmt.Errorf("update tasks status error: %w", err)
			}
		}
		return nil
	})
}

func (m *TaskRunnerMgr) resumeDownloadTaskCb(ids []int64) error {
	err := m.fileRunner.opWithLock(true, func() (err error) {
		ids, err = service.FindTasksByStatus(model.TaskType_DownloadFile, canResumeStatus, ids)
		if len(ids) > 0 {
			err = service.UpdateTasksStatus(model.TaskType_DownloadFile, canResumeStatus, model.TaskStatus_Waiting, "", ids)
			if err != nil {
				err = fmt.Errorf("update tasks status error: %w", err)
			}
			sendTaskStatusChangeByIds(model.TaskType_DownloadFile, TaskMgrOpTypeResumeTask, ids)
		}
		return nil
	})
	if err == nil {
		m.fileRunner.notifyTask()
	}
	return err
}

func downloadOpHandler(ctx *gin.Context, opFunc func([]*model.TaskId) error) {
	query := new(TaskIdsQuery)
	err := ctx.BindJSON(query)
	if err != nil {
		zap.L().Info("error to bind json query", zap.Error(err))
		BadParameter(ctx)
		return
	}
	err = opFunc(query.Ids)
	if err != nil {
		BadRequest(ctx, err.Error())
		return
	} else {
		Success(ctx, nil, "")
	}
}

func deleteDownloadTask(ctx *gin.Context) {
	downloadOpHandler(ctx, downloadTaskRunnerMgr.DeleteTask)
}

func pauseDownloadTask(ctx *gin.Context) {
	downloadOpHandler(ctx, downloadTaskRunnerMgr.PauseTask)
}

func resumeDownloadTask(ctx *gin.Context) {
	downloadOpHandler(ctx, downloadTaskRunnerMgr.ResumeTask)
}

type AddTaskFileItem struct {
	IsDir bool   `json:"is_dir"`
	Name  string `json:"name"`
	Size  int64  `json:"size"`
}

type AddTaskQuery struct {
	DeviceID   uint64             `json:"device_id"`
	DevicePath string             `json:"device_path"`
	LocalPath  string             `json:"local_path"`
	Files      []*AddTaskFileItem `json:"files"`
}

func addDownloadTask(ctx *gin.Context) {
	query := new(AddTaskQuery)
	err := ctx.BindJSON(query)
	if err != nil {
		zap.L().Info("error to bind json", zap.Error(err))
		BadParameter(ctx)
		return
	}
	err = downloadTaskRunnerMgr.AddTasks(query)
	if err != nil {
		BadRequest(ctx, err.Error())
	} else {
		Success(ctx, nil, "")
	}
}
